package com.hzit.hzb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hzit.common.exception.Assert;
import com.hzit.common.result.ResponseEnum;
import com.hzit.hzb.core.enums.LendStatusEnum;
import com.hzit.hzb.core.enums.ReturnMethodEnum;
import com.hzit.hzb.core.enums.TransTypeEnum;
import com.hzit.hzb.core.hfb.HfbConst;
import com.hzit.hzb.core.hfb.RequestHelper;
import com.hzit.hzb.core.mapper.UserAccountMapper;
import com.hzit.hzb.core.pojo.entity.*;
import com.hzit.hzb.core.mapper.LendMapper;
import com.hzit.hzb.core.pojo.vo.BorrowInfoApprovalVO;
import com.hzit.hzb.core.pojo.vo.BorrowerDetailVO;
import com.hzit.hzb.core.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzit.hzb.core.util.*;
import com.sun.nio.sctp.SendFailedNotification;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.xml.stream.events.EndDocument;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author hbxfwf
 * @since 2024-08-27
 */
@Service
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

	@Autowired
	private DictService dictService;
	@Autowired
	private BorrowerService borrowerService;
	@Autowired
	private UserAccountService userAccountService;
	@Autowired
	private TransFlowService transFlowService;
	@Autowired
	private UserAccountMapper userAccountMapper;
	@Autowired
	private UserInfoService userInfoService;
	@Autowired
	private LendItemService lendItemService;
	@Autowired
	private LendReturnService lendReturnService;
	@Autowired
	private LendItemReturnService lendItemReturnService;
	/**
	 * 产生标的
	 * @param vo
	 * @param borrowInfo
	 */
	@Override
	public void createLend(BorrowInfoApprovalVO vo, BorrowInfo borrowInfo) {
		// 1. 构造一个标的对象
		Lend lend = new Lend();
		// 1.1 设置标的id
		lend.setLendNo(LendNoUtils.getLendNo());
		// 1.2 设置标的金额
		lend.setAmount(borrowInfo.getAmount());
		// 1.3 设置借款id
		lend.setBorrowInfoId(borrowInfo.getId());
		// 1.4 设置审核用户id
		lend.setCheckAdminId(1l);
		// 1.5 设置标的产生的时间
		lend.setCreateTime(LocalDateTime.now());
		// 1.6 设置标的审核时间
		lend.setCheckTime(LocalDateTime.now());
		String lendStartDateStr = vo.getLendStartDate();
		LocalDate lendStartDate = LocalDate.parse(lendStartDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		// 1.7 设置标的开始时间
		lend.setLendStartDate(lendStartDate);
		// 1.8 获取期数
		Integer period = borrowInfo.getPeriod();
		LocalDate endStartDate = lendStartDate.plusMonths(period);
		// 1.9 设置标的结束时间
		lend.setLendEndDate(endStartDate);
		// 1.10 设置标的期数
		lend.setPeriod(period);
		// 1.11 设置借款用户的id
		lend.setUserId(borrowInfo.getUserId());
		// 1.12 设置年利化率
		lend.setLendYearRate(vo.getLendYearRate().divide(new BigDecimal(100)));
		// 1.13 设置服务费率
		lend.setServiceRate(vo.getServiceRate().divide(new BigDecimal(100)));
		// 1.14 设置标的标题
		lend.setTitle(vo.getTitle());

		// 1.15 平台预期收益
		// 1.15.1 计算月年化率
		// divide参数说明：
		// ① 参数1：代表除数
		// ② 参数2：代表保留的小数位数
		// ③ 参数3：代表保留小数时的模式，我们这里用的是将8位后的小数删除
		BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, RoundingMode.DOWN);
		// 1.15.2 计算期望收益
		lend.setExpectAmount(lend.getAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod())));

		// 1.16 设置标的的描述信息
		lend.setLendInfo(vo.getLendInfo());
		// 1.17 设置己投金额
		lend.setInvestAmount(new BigDecimal(0));
		// 1.18 设置己投人数
		lend.setInvestNum(0);
		// 设置最低投资金额
		lend.setLowestAmount(new BigDecimal(100));
		// 1.19 设置放款时间
		// lend.setPaymentTime(LocalDateTime.now());
		// 1.20 设置放款人id
		// lend.setPaymentAdminId();
		// 1.20 设置状态
		lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());
		// 1.21 设置实际收益
		lend.setRealAmount(new BigDecimal(0));
		// 1.22 设置还款方式
		lend.setReturnMethod(borrowInfo.getReturnMethod());
		// 1.23 设置发布日期
		lend.setPublishDate(LocalDateTime.now());
		// 最后,保存
		this.save(lend);

	}

	/**
	 * 处理借款信息
	 * @return
	 */
	@Override
	public List<Lend> selectList() {
		List<Lend> list = this.list();
		for (Lend lend : list) {
			lend.getParam().put("status",LendStatusEnum.getMsgByStatus(lend.getStatus()));
			String returnMethod = borrowerService.getDictNameByDictCodeAndValue("returnMethod", lend.getReturnMethod() + "");
			lend.getParam().put("returnMethod",returnMethod);
		}
		return list;
	}

	/**
	 * 根据标的id查询标的与借款人详细信息
	 * @param id
	 * @return
	 */
	@Override
	public Map<String, Object> show(Long id) {
		// 第一步:封装lend信息
		// 1. 根据标的id,查询出标的信息
		Lend lend = this.getById(id);
		// 1.2 重新组织标的数据
		lend.getParam().put("status",LendStatusEnum.getMsgByStatus(lend.getStatus()));
		lend.getParam().put("returnMethod",borrowerService.getDictNameByDictCodeAndValue("returnMethod",lend.getReturnMethod() + ""));

		// 第二步:封装借款人信息
		// 2.1 得到用户id
		Long userId = lend.getUserId();
		// 2.2 查询出借款人id
		Borrower borrower = borrowerService.getOne(new QueryWrapper<Borrower>().eq("user_id", userId));
		// 2.3 得到借款人id
		Long borrowerId = borrower.getId();
		// 2.4 根据借款人id获取借款人详细信息
		BorrowerDetailVO borrowerDetailVO = borrowerService.getDetailsById(borrowerId);

		// 第三部分:将上面两部分数据放到map中
		Map<String,Object> result = new HashMap<>();
		result.put("lend",lend);
		result.put("borrower",borrowerDetailVO);

		// 最后,返回
		return result;
	}

	/**
	 * 查询标的列表
	 * @return
	 */
	@Override
	public List<Lend> findAll() {
		List<Lend> list = this.list();
		for (Lend lend : list) {
			lend.getParam().put("returnMethod",borrowerService.getDictNameByDictCodeAndValue("returnMethod",lend.getReturnMethod() + ""));
			lend.getParam().put("status",LendStatusEnum.getMsgByStatus(lend.getStatus()));
		}
		return list;
	}

	/**
	 * 放款
	 * 业务功能：
	 * 1、向汇付宝发送放款请求并获取返回的数据
	 * 2、修改借款人的账户信息(user_account)
	 * 3、向交易流水表(trans_flow)添加借款人交易流水信息
	 * 4、根据lendid及状态查询投资项(lend_item)列表
	 * 5、在投资项列表中修改投资人的账户信息(user_account)
	 * 6、在投资人项列表中保存投资人交易流水信息。
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void makloan(Long id) {
		// 0. 查询标的信息
		Lend lend = this.getById(id);
		/*------------------------------------第一部分： 向汇付宝发送放款请求并获取返回的数据 --------------------------*/
		Map<String, Object> paramMap = new HashMap<>();
		// 1.1 拼装请求参数
		paramMap.put("agentId",HfbConst.AGENT_ID);      // 商户id
		paramMap.put("agentProjectCode",lend.getLendNo());      // 标的编号
		String agentBillNo = LendNoUtils.getLoanNo();//放款编号
		paramMap.put("agentBillNo",agentBillNo);
		// 1.2 计算月年化率
		//平台收益，放款扣除，借款人借款实际金额=借款金额-平台收益
		//月年化
		BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
		// 计算平台收益
		//平台实际收益 = 已投金额 * 月年化 * 标的期数
		BigDecimal realAmount = lend.getInvestAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));
		paramMap.put("mchFee",realAmount);     // 平台收益
		paramMap.put("note","放款备注");         // 备注
		paramMap.put("timestamp",RequestHelper.getTimestamp()); // 时间戳
		String sign = RequestHelper.getSign(paramMap);          // 生成验签串
		paramMap.put("sign",sign);                              // 添加验签参数
		// 1.3 向汇付宝平台发送请求
		JSONObject jsonObject = RequestHelper.sendRequest(paramMap, HfbConst.MAKE_LOAD_URL);

		/*------------------------------------第二部分： 修改借款人的账户信息(user_account) --------------------------*/
		// 2.1 得到返回结果参数码
		String result = jsonObject.getString("resultCode");
		// 2.2 获取借款人id并查询出借款人信息
		Long userId = lend.getUserId();
		// 得到借款人信息
		UserInfo userInfo = userInfoService.getById(userId);
		// 2.3 如果返回参数码为0000，证明返回成功
		Assert.isTrue("0000".equals(result), ResponseEnum.WEIXIN_CALLBACK_PARAM_ERROR);

		// 2.4 查询出借款人的账户信息
		// UserAccount userAccount = userAccountService.getOne(new QueryWrapper<UserAccount>().eq("user_id",userId));
		// 2.5 修改借款人信息
		BigDecimal voteAmt = new BigDecimal(jsonObject.getString("voteAmt"));
		userAccountMapper.updateUserAccount(voteAmt,userInfo.getBindCode(),new BigDecimal(0));

		// 2.6 修改标的信息
		lend.setStatus(LendStatusEnum.PAY_RUN.getStatus()); // 付款中。。。
		lend.setPaymentTime(LocalDateTime.now());           // 支付时间。。。
		lend.setRealAmount(realAmount);                     // 平台实际收益
		this.updateById(lend);

		// 2.7 添加借款人流水信息
		TransFlow transFlow = new TransFlow();
		transFlow.setUserId(userId);
		transFlow.setTransAmount(voteAmt);
		transFlow.setTransNo(agentBillNo);
		transFlow.setTransType(TransTypeEnum.BORROW_BACK.getTransType());
		transFlow.setTransTypeName(TransTypeEnum.BORROW_BACK.getTransTypeName());
		transFlow.setUserName(userInfo.getName());
		transFlow.setMemo( "借款放款到账，编号：" + lend.getLendNo());
		transFlowService.save(transFlow);

		/*------------------------------------第三部分： 根据lendid及状态查询投资项(lend_item)列表 --------------------------*/
		// 3.1 获取lendItemList集合
		List<LendItem> lendItemList = lendItemService.list(new QueryWrapper<LendItem>().eq("lend_id", lend.getId()));
		for (LendItem lendItem : lendItemList) {
		/*------------------------------------第四部分： 在投资项列表中修改投资人的账户信息(user_account) --------------------------*/
			// 4.1 在投资项列表中修改投资人的账户信息(user_account)
			Long investUserId = lendItem.getInvestUserId();
			// 4.2 查询投资人用户信息
			userInfo = userInfoService.getById(investUserId);
			// 4.3 修改投资人的账户信息
			userAccountMapper.updateUserAccount(new BigDecimal(0),userInfo.getBindCode(),lendItem.getInvestAmount().negate());

		/*------------------------------------第五部分： 新增投资人交易流水 --------------------------*/
			transFlow = new TransFlow();
			transFlow.setUserName(userInfo.getName());
			transFlow.setTransType(TransTypeEnum.INVEST_UNLOCK.getTransType());     // 交易类型
			transFlow.setTransTypeName(TransTypeEnum.INVEST_UNLOCK.getTransTypeName()); // 交易名字
			transFlow.setUserId(investUserId);
			transFlow.setTransNo(LendNoUtils.getTransNo());
			transFlow.setTransAmount(lendItem.getInvestAmount());       // 交易金额
			transFlow.setCreateTime(LocalDateTime.now());
			transFlow.setMemo("冻结资金转出，出借放款，编号：" + lend.getLendNo());
			transFlowService.save(transFlow);

		}
		//放款成功生成借款人还款计划和投资人回款计划
		//todo..
		repaymentPlan(lend);
	}



	/**
	 * 还款计划
	 * lend_item_return: 回款记录表
	 * lend_return: 还款记录表
	 * @param lend
	 */
	private void repaymentPlan(Lend lend) {
		// 第一步： 生成并添加还款计划
		List<LendReturn> lendReturnList = new ArrayList<>();
		// 1.1 获取标的的期数
		Integer period = lend.getPeriod();
		// 1.2 根据期数生成还款计划
		for (int i = 1; i <= period; i++) {
			// 1.2.1 生成一个还款计划对象
			LendReturn lendReturn = new LendReturn();
			lendReturn.setAmount(lend.getAmount());             // 借款总金额
			lendReturn.setBaseAmount(lend.getInvestAmount());   // 计息本金额
			lendReturn.setBorrowInfoId(lend.getBorrowInfoId()); // 借款id
			lendReturn.setCurrentPeriod(i);                     // 当前期数
			lendReturn.setFee(new BigDecimal(0));           // 手续费
			lendReturn.setLendId(lend.getId());                 // 设置标的id
			lendReturn.setLendYearRate(lend.getLendYearRate()); // 设置年利率
			lendReturn.setReturnNo(LendNoUtils.getReturnNo());  // 设置还款编号
			lendReturn.setStatus(0);                            // 未还款
			lendReturn.setUserId(lend.getUserId());             // 设置借款用户id
			lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));  // 设置还款日期
			lendReturn.setOverdue(false);                       // 是否逾期
			lendReturn.setOverdueTotal(new BigDecimal(0));  // 逾期金额
			lendReturn.setReturnMethod(lend.getReturnMethod()); // 还款方式
			if(i == lend.getPeriod()){      // 相当于最后一个月
				lendReturn.setLast(true);
			}else{
				lendReturn.setLast(false);
			}
			// 最后，添加这个还款计划对象到集合中
			lendReturnList.add(lendReturn);
		}
		// 1.3 保存还款记划
		lendReturnService.saveBatch(lendReturnList);

		// 第二步：保存回款计划
		// 2.1 获取lendReturnList中还款期数与还款计划id对应map
		Map<Integer, Long> lendReturnMap = lendReturnList.stream()
				.collect(Collectors.toMap(
						LendReturn::getCurrentPeriod,       // map的key（当前期数）
						LendReturn::getId                   // map的值(还款id)
		));
		//2.2 回款计划列表( 一个还款记录（lend_return）对应多个回款记录(lend_item_return) )
		// 2.2.1 定义回款计划列表
		List<LendItemReturn> lendItemReturnAllList = new ArrayList<>();
		// 2.2.2 获取投资成功的投资记录
		List<LendItem> lendItemList = lendItemService.selectByLendId(lend.getId(),1);
		// 2.2.3 遍历标的项列表
		for (LendItem lendItem : lendItemList) {
			//创建回款计划列表
			List<LendItemReturn> lendItemReturns = this.returnInvest(lendItem.getId(), lendReturnMap, lend);
			lendItemReturnAllList.addAll(lendItemReturns);
		}
		// 2.3 计算一次还款的多个回款总金额
		for (LendReturn lendReturn : lendReturnList) {
			// 2.3.1 计算总的本金
			// BigDecimal sumPrincipal = lendItemReturnAllList.stream()
			// 		.filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
			// 		.map(LendItemReturn::getPrincipal)
			// 		.reduce(BigDecimal.ZERO, BigDecimal::add);
			// lendReturn.setPrincipal(sumPrincipal);
			// 相当于：
			BigDecimal sumPrincipal = new BigDecimal(0);
			for (LendItemReturn lendItemReturn : lendItemReturnAllList) {
				if(lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue()){
					sumPrincipal.add(lendItemReturn.getPrincipal());
				}
			}
			lendReturn.setPrincipal(sumPrincipal);

			// 2.3.2 设置总的利息
			BigDecimal sumInterest = lendItemReturnAllList.stream()
					.filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
					.map(LendItemReturn::getInterest)
					.reduce(BigDecimal.ZERO, BigDecimal::add);
			lendReturn.setInterest(sumInterest);

			// 2.3.3 计算本息
			// BigDecimal total = lendItemReturnAllList.stream().filter(lendItemReturn -> lendItemReturn.getLendReturnId().longValue() == lendReturn.getId().longValue())
			// 		.map(LendItemReturn::getInterest)
			// 		.reduce(BigDecimal.ZERO, BigDecimal::add);
			lendReturn.setTotal(sumInterest.add(sumPrincipal));
		}
		// 2.4 批量修改还款计划表
		lendReturnService.updateBatchById(lendReturnList);
	}

	/**
	 * 生成回款计划列表
	 * @param id            标的记录
	 * @param lendReturnMap 标的期数与还款记录id
	 * @param lend          标的对象
	 * @return
	 */
	private List<LendItemReturn> returnInvest(Long id, Map<Integer, Long> lendReturnMap, Lend lend) {
		// 1.1 查询出标的记录对象
		LendItem lendItem = lendItemService.getById(id);
		// 1.2 定义回款计划列表
		List<LendItemReturn> lendItemReturnList = new ArrayList<>();
		// 1.3 根据总借款额，年利率，还款总月数获取总利息
		BigDecimal invest = lendItem.getInvestAmount();         // 总借款额
		BigDecimal yearRate = lendItem.getLendYearRate();       // 年利率
		int totalMonth = lend.getPeriod();                      // 总期数

		// 1.4 获取还款期数---》利息、还款期数--》本金（数据）
		Map<Integer, BigDecimal> mapInterest = null;              //还款期数 -> 利息
		Map<Integer, BigDecimal> mapPrincipal = null;             //还款期数 -> 本金
		if(lend.getReturnMethod().intValue() == ReturnMethodEnum.ONE.getMethod().intValue()){
			mapInterest = Amount1Helper.getPerMonthInterest(invest, yearRate, totalMonth);
			mapPrincipal = Amount1Helper.getPerMonthPrincipal(invest, yearRate, totalMonth);
		}else if(lend.getReturnMethod().intValue() == ReturnMethodEnum.TWO.getMethod().intValue()){
			mapInterest = Amount2Helper.getPerMonthInterest(invest, yearRate, totalMonth);
			mapPrincipal = Amount2Helper.getPerMonthPrincipal(invest, yearRate, totalMonth);
		}else if(lend.getReturnMethod().intValue() == ReturnMethodEnum.THREE.getMethod().intValue()){
			mapInterest = Amount3Helper.getPerMonthInterest(invest, yearRate, totalMonth);
			mapPrincipal = Amount3Helper.getPerMonthPrincipal(invest, yearRate, totalMonth);
		}else if(lend.getReturnMethod().intValue() == ReturnMethodEnum.FOUR.getMethod().intValue()){
			mapInterest = Amount4Helper.getPerMonthInterest(invest, yearRate, totalMonth);
			mapPrincipal = Amount4Helper.getPerMonthPrincipal(invest, yearRate, totalMonth);
		}

		// 1.5 构造回款计划数据
		Set<Integer> keySet = mapInterest.keySet();
		for (Integer key : keySet) {        // key: 期数
			BigDecimal interest = mapInterest.get(key); // 获取利息
			BigDecimal principal = mapPrincipal.get(key); // 获取本金
			Long lendReturnId = lendReturnMap.get(key); // 还款计划id
			// 1.5.1 定义回款计划对象
			LendItemReturn lendItemReturn = new LendItemReturn();
			lendItemReturn.setCurrentPeriod(key);       // 设置当前期数
			lendItemReturn.setFee(new BigDecimal(0));   // 设置手续费
			if(lend.getPeriod().intValue() > key.intValue()) {
				lendItemReturn.setPrincipal(principal);     // 设置本金
			}else{      //最后一期应还本金 = 用当前投资人的总投资金额 - 除了最后一期前面期数计算出来的所有的应还本金
				// 得到除了本期外的本金
				BigDecimal principalAmount = lendItemReturnList.stream().map(LendItemReturn::getPrincipal).reduce(BigDecimal.ZERO,BigDecimal::add);
				BigDecimal lastPrincipal = lendItem.getInvestAmount().subtract(principalAmount);
				lendItemReturn.setPrincipal(lastPrincipal);
			}
			lendItemReturn.setInterest(interest);       // 设置利息
			lendItemReturn.setInvestAmount(lendItem.getInvestAmount()); // 设置投资金额
			lendItemReturn.setInvestUserId(lendItem.getInvestUserId()) ; // 设置用户id
			lendItemReturn.setLendItemId(lendItem.getId());             // 标的记录id
			lendItemReturn.setLendId(lend.getId());     // 设置标的id
			lendItemReturn.setLendReturnId(lendReturnId);   // 设置还款计划id
			lendItemReturn.setReturnMethod(lend.getReturnMethod()); // 还款方式
			lendItemReturn.setReturnDate(lendItem.getLendStartDate().plusMonths(key));
			lendItemReturn.setStatus(0);    // 未归还
			lendItemReturn.setLendYearRate(lend.getLendYearRate()); // 设置年利率
			lendItemReturn.setTotal(interest.add(principal));  // 本息
			lendItemReturn.setOverdue(false);   // 是否逾期

			// 添加到集合中
			lendItemReturnList.add(lendItemReturn);
		}
		// 1.6 批量保存回款计划
		lendItemReturnService.saveBatch(lendItemReturnList);

		// 1.7 返回
		return lendItemReturnList;
	}
}
